Weaving maps of multivariate data
Preliminaries
We’ve been working in R for the ‘one-stop-shop’ mix of coding, handling spatial data, and visualization
It’s work in progress, so these aren’t slides, more an annotated RMarkdown notebook
This section gives a sense of the requirements
Source code
Our code lives in these files
source("weaving-space-utils.R")
source("biaxial-weave-units.R")
source("triaxial-weave-units.R")
source("weave-map.R")Libraries
And here, for reference are the packages we’ve used (so far!)
library(sf) # vector spatial data
library(wk) # WKT features and affine transforms
library(tmap) # thematic maps
library(dplyr) # data wrangling
library(pracma) # more matrix stuffAnd some more used to make example maps of other approaches
library(ggplot2) # plotting
library(tricolore) # trivariate choropleths
library(ggtern) # trivariate legendSome example data to mess with
region <- st_read("data/vax-auckland-20211006.gpkg")## Reading layer `vax-auckland-20211006' from data source
## `/home/osullid3/Documents/code/weaving-space/data/vax-auckland-20211006.gpkg'
## using driver `GPKG'
## Simple feature collection with 155 features and 23 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: 1748016 ymin: 5907966 xmax: 1768438 ymax: 5922015
## Projected CRS: NZGD2000 / New Zealand Transverse Mercator 2000
Note that for our method we need this in part so we can match its CRS
Motivation
Increasingly, we deal with highly multivariate data.
Many approaches can be used to visualise these in spatial contexts, but it’s always challenging. Often we resort to small multiple displays, where each attribute is presented as an individual (small) map. Or perhaps more often, we finding ourselves flipping back and forward among many layers in a GIS or other tool.
Wouldn’t it be nice to see multiple attributes together?! Perhaps to be able to identify patterns across more than one attribute in combination.
We’re not the first to think this, so there are plenty of approaches already around…
Small multiples
This is sf’s default plot output for a dataset
plot(region)
Bivariate choropleths
A crude approach uses alpha values for each attribute, e.g., in tmap
tm_shape(region) +
tm_polygons(col = "dose2_uptake", palette = "-Reds", alpha = 0.65,
title = "Dose 2 per thousand ") +
tm_shape(region) +
tm_polygons(col = "pAsian", palette = "Blues", alpha = 0.35,
title = "% Asian") +
tm_layout(legend.outside = TRUE)
Or, use something like Jan Caha’s QGIS plugin which implements the approach described by Joshua Stevens in this post
bivariate choropleth
Trivariate choropleths
Mixing three colours is hard, but e.g., the tricolore package can do this…
eth_mix <- tricolore::Tricolore(
region, p1 = "pEuropean", p2 = "pMaori", p3 = "pAsian", breaks = 5
)
region$eth_mix_tri <- eth_mix$rgb
ggplot(region) +
geom_sf(aes(fill = eth_mix_tri)) +
scale_fill_identity() +
annotation_custom(
grob = ggplotGrob(eth_mix$key + labs(L = 'Pākehā', T = 'Māori', R = 'Asian')),
xmin = 1.7465e6, xmax = 1.7535e6, ymin = 5.9075e6, ymax = 5.9125e6)
Symbols over choropleths
A simple example
choropleth with symbols
Multivariate symbols
The classic example here is Dorling’s Chernoff faces map of the UK 1987 election
Figure 8.10 Dorling D. 2012. The visualisation of spatial social structure. Chichester, England: John Wiley & Sons.
Multi-element patterns
There are many variations on this idea, but perhaps the most common is a categorical dot map
This example made using tmap and data preparation code from James Smythe’s cultureofinsight blog
dot map of ethnic composition
Our idea
A weave pattern where each ‘thread’ or ‘ribbon’ (the warp and weft) represents a different attribute that can be independently symbolised
from Chaves LF, MD Friberg, LA Hurtado, R Marín Rodríguez, D O’Sullivan, and LR Bergmann. 2021 (online first). Trade, uneven development and people in motion: Used territories and the initial spread of COVID-19 in Mesoamerica and the Caribbean. Socio-Economic Planning Sciences.
This was done with SVG symbol fills in QGIS, and was (very!) fiddly to produce
But it got us thinking about the wider possibilities…
A flax kete
Preliminary implementation
- Regular rectangular or hex grids of points generated by geospatial tools
tiling a weave unit
- What repeatable units can tile across such grids to give the appearance of a woven pattern?
We have proof-of-concept R tools to make weave patterns:
Weave units
Biaxial weaves
Plain weaves
Traditional weave patterns with threads in two directions, the warp and the weft. These are all generated drawing on the matrix multiplication underpinnings of weaving!
The simplest case is a plain weave.
rect11_unit <- ## plain weave example
get_biaxial_weave_unit(spacing = 300, type = "plain",
ids = "a|b", crs = st_crs(region))
rect11_unit$primitive %>% plot(lwd = 0.01)
This could be useful if clearly distinct palettes were used in the warp and weft elements. More useful is if we change the aspect to the warp and weft elements so we can distinguish directions.
rect11_unit <- ## plain weave example
get_biaxial_weave_unit(spacing = 300, aspect = 0.8,
ids = "ab|cd", crs = st_crs(region))
rect11_unit$primitive %>% plot(lwd = 0.01)
More threads
This is highly customisable
rect32_unit <-
get_biaxial_weave_unit(spacing = 150, aspect = sqrt(0.5),
ids = "abc|de", crs = st_crs(region))
rect32_unit$primitive %>% plot(lwd = 0.01)
Missing threads
We can even leave gaps or duplicate threads
rect34_unit <- ## plain weave example
get_biaxial_weave_unit(spacing = 300, aspect = 0.8,
ids = "ab-|cc-d", crs = st_crs(region))
rect34_unit$primitive %>% plot(lwd = 0.01)
Twill weaves and basket weaves
There are near infinite possibilities here…
twill_unit <-
get_biaxial_weave_unit(spacing = 200, type = "twill", n = 2,
aspect = 0.6, ids = "ab|cd", crs = st_crs(region))
twill_unit$primitive %>% plot(lwd = 0.01)
basket_unit <-
get_biaxial_weave_unit(spacing = 200, type = "basket", n = 2,
aspect = 0.8, ids = "ab|cd", crs = st_crs(region))
basket_unit$primitive %>% plot(lwd = 0.01)
Other weaves
And here is another example (this makes use of the capability to generate any weavable repeat pattern, but the pattern is hardcoded for now…)
this_unit <-
get_biaxial_weave_unit(spacing = 200, type = "this", aspect = 0.8,
ids = "a|b", crs = st_crs(region))
this_unit$primitive %>% plot(lwd = 0.01)
Triaxial weaves
Hexagonal
We can also make weaves with threads running in 3 directions. This example is based on a hexagonal tileable unit, and can allow for more than one thread in each direction
hex_unit <- ## hex example
get_triaxial_weave_unit(spacing = 600, margin = 2,
ids = "a|b|cd", type = "hex", crs = st_crs(region))
hex_unit$primitive %>% plot(lwd = 0.01)
A cube is another option, although this produces some odd 3D effects when tiled
cube_unit <-
get_triaxial_weave_unit(spacing = 600, ids = "ab|cd|ef", margin = 5,
type = "cube", crs = st_crs(region))
cube_unit$primitive %>% plot(lwd = 0.01)
Diamond
An alternative way to produce a triangular weave is with a diamond repeating unit with angles 60° and 120°
diamond_unit <- ## diamond example
get_triaxial_weave_unit(spacing = 600, margin = 10,
ids = "a|b|c", type = "diamond", crs = st_crs(region))
diamond_unit$primitive %>% plot(lwd = 0.01)
Tiling diamond units and transforming weaves
Tiling the diamond unit is slightly more involved than rectangular or hexagonal units:
- transform to a rectangle (applying the same transformation to the map)
- perform the tiling (rectangularly)
- invert the transformation
We allow for any of the weave unit ‘tiles’ to be transformed in this way.
For example we can transform the region to tile like this
region[, 2] %>% sf_diamond_to_square() %>% tm_shape() + tm_polygons()
then tile it with any weave unit, and transform back to the original map coordinates. This allows us to (say) take the twill tile above and apply it in this form:
twill_unit$primitive %>% sf_square_to_diamond() %>% plot(lwd = 0.01)
Weave a map
Now ‘weave’ the map with one of the weave unit tiles applied to the region
We can also optionally specify a rotation and an affine transformation
cloth <- weave_layer(twill_unit, region, angle = 30)
# cloth <- weave_layer(weave_unit, region,
# transform = affine_abcd(1, 0, 0.5, 1))Make the map
Do this in tmap “view” mode for a zoomable web map.
tmap_mode("view")Split the data by the id so that it is convenient to symbolise them separately
layers <- cloth %>% split(as.factor(cloth$id))- Each
idvalue can be symbolised separately using symbolisation the data can support - We can also plot the region data as a choropleth if desired
tm_shape(region, name = "Dose 2 uptake") +
tm_fill(col = "dose2_uptake", palette = "inferno", style = "cont", title = "Dose 2 per 1000") +
tm_shape(layers$a, name = "Pākehā") +
tm_fill(col = "pEuropean", palette = "Greys", title = "% Pākehā", n = 3) +
tm_shape(layers$b, name = "Māori") +
tm_fill(col = "pMaori", palette = "Reds", title = "% Māori", n = 3) +
tm_shape(layers$c, name = "Pasifika") +
tm_fill(col = "pPacific", palette = "Purples", title = "% Pasifika", n = 3) +
tm_shape(layers$d, name = "Asian") +
tm_fill(col = "pAsian", palette = "Greens", title = "% Asian", n = 3) +
tm_basemap(server = "CartoDB.VoyagerNoLabels")Another example
Write the weave layers
We can also save out to a multi-layer GPKG for use in any tool
write_weave_layers(cloth, region, "data/cloth.gpkg")Further work
There is lots to do (at least potentially!)
- Clarify the API for the tools to make their usage clearer
- Figure out how to make legends…
- Develop guidelines for what works
- How does colour work in this setting (how many, what combinations?)
- Explore what symbolisations work (continuous, classified, categorical?)
- Understand better how orientation operates
Acknowledgments
- Thanks to co-conspirator Luke Bergmann (UBC)
- You’ll find code at github.com/DOSull/weaving-space